#pragma once

#include "net_common.hpp"
#include "net_tsdeque.hpp"
#include "net_message.hpp"
#include "net_connection.hpp"

namespace dw
{
    namespace net
    {
        template <typename T>
        class server_interface
        {
        protected:
            tsdeque<owned_message<T>> m_qMsgIn;

            // Order of declaration is important
            // which is also the order of initialisation
            asio::io_context m_asioContext;
            std::thread m_threadContext;

            // These things need an asio context
            asio::ip::tcp::acceptor m_asioAcceptor;

            // Container of active validated connections
            std::deque<std::shared_ptr<connection<T>>> m_deqConnections;

            // Clients will be identified in the "wider system" via an ID
            uint32_t nIDCounter;

        public:
            server_interface(uint16_t port) : 
                m_asioAcceptor(m_asioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port))
            {
                nIDCounter = 10000;
            }

            virtual ~server_interface()
            {
                Stop();
            }

            bool Start()
            {
                try
                {
                    // Give context a job to do
                    WaitForClientConnection();
                    // Start thread in which accepts client connections
                    m_threadContext = std::thread([this]() { m_asioContext.run(); });
                }
                catch (std::exception &e)
                {
                    std::cerr << "[SERVER] Exception: " << e.what() << std::endl;
                    return false;
                }

                std::cout << "[SERVER] Started!" << std::endl;
                return true;
            }

            void Stop()
            {
                m_asioContext.stop();

                if (m_threadContext.joinable())
                {
                    m_threadContext.join();
                }

                std::cout << "[SERVER] Stopped!" << std::endl;
            }

            //! ASYNC - Instruct asio to wait for connection
            void WaitForClientConnection()
            {
                m_asioAcceptor.async_accept(
                    [this](std::error_code ec, asio::ip::tcp::socket socket)
                    {
                        if (!ec)
                        {
                            std::cout << "[SERVER] New Connection: " << socket.remote_endpoint() << std::endl;

                            std::shared_ptr<connection<T>> newconn = std::make_shared<connection<T>>(
                                connection<T>::owner::server,
                                m_asioContext,
                                std::move(socket),
                                m_qMsgIn
                            );

                            // Give the user server a chance to deny connection
                            if (OnClientConnect(newconn))
                            {
                                m_deqConnections.push_back(std::move(newconn));
                                m_deqConnections.back()->ConnectToClient(this, nIDCounter++);
                                std::cout << "[SERVER] Connection <" << m_deqConnections.back()->GetID() << "> Connected!" << std::endl;
                            }
                            else
                            {
                                std::cout << "[SERVER] Connection Denied!" << std::endl;
                            }
                        }
                        else
                        {
                            std::cerr << "[SERVER] New Connection Error: " << ec.message() << std::endl;
                        }

                        // Waiting for another connection
                        WaitForClientConnection();
                    }
                );
            }

            void MessageClient(std::shared_ptr<connection<T>> client, const message<T> &msg)
            {
                if (client && client->isConnected())
                {
                    client->Send(msg);
                }
                else
                {
                    OnClientDisconnect(client);
                    client.reset(); // client is now nullptr
                    m_deqConnections.erase(std::remove(m_deqConnections.begin(),
                                                       m_deqConnections.end(),
                                                       client),
                                           m_deqConnections.end());
                }
            }

            void MessageAllClients(
                const message<T> &msg, 
                std::shared_ptr<connection<T>> pIgnoreClient = nullptr
            ) {
                bool bInvalidClientExists = false;

                for (auto &client : m_deqConnections)
                {
                    if (client && client->IsConnected())
                    {
                        if (client != pIgnoreClient)
                        {
                            client->Send(msg);
                        }
                    }
                    else
                    {
                        OnClientDisconnect(client);
                        // disconnected clients are reset to nullptr
                        client.reset();
                        bInvalidClientExists = true;
                    }
                }

                // Avoid modifying the container
                // when iterating through it
                if (bInvalidClientExists)
                {
                    m_deqConnections.erase(std::remove(m_deqConnections.begin(),
                                                       m_deqConnections.end(),
                                                       nullptr),
                                           m_deqConnections.end());
                }
            }

            void Update(uint32_t nMaxMsg = -1, bool bWait = false)
            {
                // We don't want the server to occupy 100% of a CPU core
                if (bWait) m_qMsgIn.wait();

                uint32_t nMsgCnt = 0;
                while (nMsgCnt < nMaxMsg && !m_qMsgIn.empty())
                {
                    auto msg = m_qMsgIn.pop_front();
                    OnMessage(msg.remote, msg.msg);
                    nMsgCnt++;
                }
            }

        protected:
            /* Functions which user should override */

            // Called when a client connects, you can veto the connection by returning false
            virtual bool OnClientConnect(std::shared_ptr<connection<T>> client)
            {
                return false;
            }

            // Called when a client appears to have disconnected
            virtual void OnClientDisconnect(std::shared_ptr<connection<T>> client)
            {
                return;
            }

            // Called when a message arrives
            virtual void OnMessage(std::shared_ptr<connection<T>> client, message<T> &msg)
            {
                return;
            }
            
        public:
            // Called when a client passed validation
            virtual void OnClientValidate(std::shared_ptr<connection<T>> client)
            {
                return;
            }
        };
    }
}